package top.rainbowecho.common.util;

import cn.hutool.core.io.IoUtil;
import cn.hutool.crypto.SecureUtil;
import com.google.common.collect.ImmutableMap;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.AntPathMatcher;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import top.rainbowecho.common.exception.BlogException;
import top.rainbowecho.common.exception.WrongTokenTypeException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.regex.Pattern;

import static cn.hutool.core.collection.CollUtil.getFirst;
import static java.lang.String.join;

/**
 * @author rainbow
 * @since 2019/12/9 21:02
 */
@Slf4j
public final class CommonUtil {
    private CommonUtil() {}

    public static String obtainVisitUrl(HttpServletRequest request) {
        String url;
        url = request.getScheme() +"://" + request.getServerName()
                + ":" +request.getServerPort()
                + request.getServletPath();
        if (request.getQueryString() != null){
            url += "?" + request.getQueryString();
        }

        log.debug("当前请求方法的访问路径为：" + url);

        return url;
    }

    /**
     * 将Duration格式的超时时间转换为Epoch格式
     *
     * @param timeout
     * @return
     */
    public static long timeoutToEpochSecond(Duration timeout) {
        LocalDateTime orderCancelTime = LocalDateTime.now().plus(timeout);
        return orderCancelTime.toEpochSecond(ZoneOffset.of("+8"));
    }

    public static boolean matchEmail(String mail) {
        Pattern pattern = Pattern.compile("\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*");
        return pattern.matcher(mail).matches();
    }

    public static boolean matchNotBlankStr(String value) {
        return StringUtils.isNotBlank(value) && !StringUtils.equals("null", value);
    }

    /**
     * 正则匹配手机号码
     *
     * @param value
     * @return
     */
    public static boolean matchMobilePhone(String value) {
        Pattern pattern = Pattern.compile("^[1][3-9][0-9]{9}$");
        return pattern.matcher(value).matches();
    }

    /**
     * 处理校验异常
     *
     * @param result 校验结果
     * @return 校验错误响应信息
     */
    public static ResponseEntity<Map<String, Object>> handleValidateError(BindingResult result) {
        List<ObjectError> allErrors = result.getAllErrors();
        String errMsg = getFirst(allErrors).getDefaultMessage();
        return new ResponseEntity<>(ImmutableMap.of(ResultKey.EXCEPTION_MESS, errMsg), HttpStatus.BAD_REQUEST);
    }

    /**
     * 对token进行解析，对解析出的值根据Key获取对应的值
     *
     * @param jwtToken 用户token
     * @param key token中的key
     * @return 对应的值，如果该key不存在，则抛出异常
     */
    public static String obtainInfoFromToken(String jwtToken, String key) throws WrongTokenTypeException {
        String[] authorizationPayload = jwtToken.split(" ");
        String tokenType = authorizationPayload[0];
        log.debug("Token 类型：" + tokenType);
        String token = authorizationPayload[1];
        log.debug("用户访问Token：" + token);

        if (!StringUtils.equals(tokenType, CommonKey.TOKEN_TYPE)) {
            throw new WrongTokenTypeException(ExceptionMessageContent.WRONG_TOKEN_TYPE);
        }

        Claims tokenDecoded = CommonUtil.tokenDecode(CommonKey.JWT_SIGN_KEY, token);
        log.debug("用户Token解析结果：" + tokenDecoded);
        String prop = (String) tokenDecoded.getOrDefault(key, StringUtils.EMPTY);
        if (StringUtils.isEmpty(prop)) {
            throw new BlogException(ExceptionMessageContent.NOT_EXIST_TOKEN_KEY);
        }
        return prop;
    }

    /**
     * 生成Redis的键名
     *
     * @param params 参数数组
     * @return 键名
     */
    public static String generateRedisKey(String... params) {
        return join(":", params);
    }

    /**
     * 解密jwt
     *
     * @param signKey 签名秘钥
     * @param token token值
     * @return token的payload
     */
    public static Claims tokenDecode(String signKey, String token) {
        return Jwts.parser().setSigningKey(signKey.getBytes(StandardCharsets.UTF_8))
                .parseClaimsJws(token)
                .getBody();
    }

    /**
     * 对图片原始数据进行base64编码
     *
     * @param is 文件输入流
     * @return base64编码结果
     * @throws IOException 图片无效
     */
    public static String parseBase64(InputStream is) throws IOException {
        if (is == null) {
            throw new IOException(ExceptionMessageContent.INVALID_IMAGE_CONTENT);
        }

        byte[] bytes = IoUtil.readBytes(is);
        return Base64.getEncoder().encodeToString(bytes);
    }

    /**
     * 生成访问Fs中文件所需的token
     *
     * @param filePath 文件的fs存储路径，不能带group信息
     * @param secretKey 生成token的秘钥
     * @param ts 秒级时间戳
     * @return 生成的token
     */
    public static String generateToken(String filePath, String secretKey, int ts)  {
        return SecureUtil.md5(filePath + secretKey + ts);
    }

    public static void jsonResponse(HttpServletResponse response, String json) throws IOException {
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        response.getWriter().write(json);
    }

    public static boolean pathMatch(HttpServletRequest request, Set<String> urls, AntPathMatcher pathMatcher) {
        boolean isFilterPath = false;
        for (String url : urls) {
            if (pathMatcher.match(url, request.getRequestURI())) {
                isFilterPath = true;
                break;
            }
        }

        return isFilterPath;
    }

    /**
     * 获取Ip地址
     * @param request request请求
     * @return IPv4地址
     */
    public static String getIpAddress(HttpServletRequest request) {
        String unknown = "unknown";
        String Xip = request.getHeader("X-Real-IP");
        String XFor = request.getHeader("X-Forwarded-For");
        if(StringUtils.isNotEmpty(XFor) && !unknown.equalsIgnoreCase(XFor)){
            //多次反向代理后会有多个ip值，第一个ip才是真实ip
            int index = XFor.indexOf(",");
            if(index != -1){
                return XFor.substring(0,index);
            }else{
                return XFor;
            }
        }
        XFor = Xip;
        if(StringUtils.isNotEmpty(XFor) && !unknown.equalsIgnoreCase(XFor)){
            return XFor;
        }
        if (StringUtils.isBlank(XFor) || unknown.equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isBlank(XFor) || unknown.equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isBlank(XFor) || unknown.equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StringUtils.isBlank(XFor) || unknown.equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (StringUtils.isBlank(XFor) || unknown.equalsIgnoreCase(XFor)) {
            XFor = request.getRemoteAddr();
        }
        return XFor;
    }

    /**
     * 生成验证码存放到redis中的key
     *
     * @param username 用户名
     * @param timestamp 时间戳
     * @return 生成的redis的key
     */
    public static String validateCodeKey(String username, long timestamp) {
        return username + "." + timestamp;
    }

    public static String generateDoctorListKey(String... params) {
        return String.join(":", params);
    }

    public static Map<String, List<String>> mapInCollectionToCollectionInMap(List<Map<String, String>> source) {
        return source.stream().map(spec -> {
            // 将string：string结构map转换为string：list结构
            HashMap<String, List<String>> valueList = new HashMap<>();
            spec.forEach((k, v) -> {
                ArrayList<String> values = new ArrayList<>();
                values.add(v);
                valueList.put(k, values);
            });
            return valueList;
        }).reduce((x, y) -> {
            // 将同一个key的value合并到一个list中去
            x.forEach((xKey, xValue) -> {
                List<String> yValue = y.get(xKey);
                if (yValue != null && !xValue.containsAll(yValue)) {
                    yValue.addAll(xValue);
                } else {
                    y.put(xKey, xValue);
                }
            });
            return y;
        }).orElseThrow(NullPointerException::new);
    }

    /**
     * 如果str属于空白字符串，则返回统一的空字符串 ""，如果不是，返回原文
     *
     * @param str
     * @return
     */
    public static String blankStrFilter(String str) {
        return (StringUtils.isBlank(str) || "null".equals(str)) ? StringUtils.EMPTY : str;
    }
}
